package org.chemodansama.fifteen;

import java.util.ArrayList;
import java.util.Random;

import org.chemodansama.engine.LogTag;
import org.chemodansama.engine.NpSoundMan;
import org.chemodansama.engine.math.NpMath;

import android.util.Log;

final class ChipField {

    public final int Dim;
    public final int N;
    
    private static final int[] ALLOWED_DIMS = {3, 4};
    
    private static final int[][] ROTATE_IND = 
        {{2, 5, 8, 1, 4, 7, 0, 3, 6}, 
         {3, 7, 11, 15, 2, 6, 10, 14, 1, 5, 9, 13, 0, 4, 8, 12}};
    
    private final ArrayList<Chip> mSlidingChips = new ArrayList<Chip>();
    private float mSlidingParam = 0;
    
    private final Chip[] mField;
    private int mMovesCnt = 0;
    
    private int mChip15 = -1;

    private ChipSlidingDir mDir = ChipSlidingDir.LOCKED;

    private final NpSoundMan mSoundMan;
    
    public ChipField(int dim, NpSoundMan sm) {
        super();
        
        Dim = dim;
        N = Dim * Dim;
        
        mField = new Chip[N];
        
        mSoundMan = sm;
        mSoundMan.addSound(R.raw.beep);
        
//        genRandomField();
//        genClassicUnresolvable();
//        genOneMoveSolvable();
        genRandomSolvableField();
        
//        resolveSolvability();
        
        for (int i = 0; i < N; i++) {
            if (mField[i].mNum == N - 1) {
                mChip15 = i;
                break;
            }
        }
    }
    
    private void genSolved() {
        for (int i = 0; i < N; i++) {
            mField[i] = new Chip();
            mField[i].mNum = i;
        }
    }
    
    @SuppressWarnings("unused")
    private void genClassicUnresolvable() {
        genSolved();
        
        mField[N - 3].mNum = N - 2;
        mField[N - 2].mNum = N - 3;
    }
    
    @SuppressWarnings("unused")
    private void genOneMoveSolvable() {
        for (int i = 0; i < N; i++) {
            mField[i] = new Chip();
            mField[i].mNum = i;
        }
        
        mField[N - 2].mNum = N - 1;
        mField[N - 1].mNum = N - 2;
    }
    
    private void genRandomSolvableField(){
        genSolved();
        
        Random g = new Random();
        
        float k = (float) Dim / 3;
        int m = (int) (100 * (1 + k * k));
        
        for (int i = 0; i < m; i++) {
            emptyChipMove(getRandomAvailableDir(g));            
        }
    }
    
    private int getRandomAvailableDir(Random gen) {
        int emptyIndex = getChipIdByNum(N - 1);
        int emptyRow = emptyIndex / Dim;
        int emptyCol = emptyIndex % Dim;
        int d = gen.nextInt(4);
        
        while (((emptyRow == 0) && (d == 0))
                || ((emptyCol == 0) && (d == 1))
                || ((emptyCol == Dim - 1) && (d == 2))
                || ((emptyRow == Dim - 1) && (d == 3))) {
            d += 3;
            d %= 4;
        }
        
        return d;    
    }
    
    private void emptyChipMove(int dir) {
        int shift = 0;
        switch (dir) {
        case 0:
            shift = -Dim;
            break;
        case 1:
            shift = -1;
            break;
        case 2:
            shift = 1;
            break;
        case 3:
            shift = Dim;
            break;
        }
        int emptyIndex = getChipIdByNum(N - 1);
        mField[emptyIndex].mNum = mField[emptyIndex+shift].mNum;
        mField[emptyIndex+shift].mNum = N - 1;
    }
    
    
    @SuppressWarnings("unused")
    private void genRandomField() {
        boolean[] busy = new boolean[N];
        
        for (int i = 0; i < N; i++) {
            busy[i] = false;
        }
        
        Random generator = new Random();
        
        for (int i = 0; i < N; i++) {
            
            mField[i] = new Chip();
            
            int r = generator.nextInt(N - i);

            int j = -1;
            while (r >= 0) {
                
                j++;

                if (!busy[j]) {
                    r--;
                }
            }
            
            if ((j < 0) || (j >= N)) {
                Log.e(LogTag.TAG, "j < 0");
            }
            
            mField[i].mNum = j;
            busy[j] = true;
        }
    }
    
    public Chip getChip(int i) {
        return mField[i % N];
    }
    
    public Chip getChipByNum(int num) {
        for (int i = 0; i < N; i++) {
            if (mField[i].mNum == num) {
                return mField[i];
            }
        }
        
        return null;
    }
    
    private ChipSlidingDir getChipDir(int chipId) {
        
        int row = chipId / Dim;
        int column = chipId % Dim;
        
        int row15 = mChip15 / Dim;
        int col15 = mChip15 % Dim;
        
        if (row == row15) {
            if (column < col15) {
                return ChipSlidingDir.RIGHT;
            } else if (column > col15) {
                return ChipSlidingDir.LEFT;
            } else {
                return ChipSlidingDir.LOCKED;
            }
        } else if (column == col15) {
            if (row < row15) {
                return ChipSlidingDir.UP;
            } else if (row > row15) {
                return ChipSlidingDir.DOWN;
            } else {
                return ChipSlidingDir.LOCKED;
            }
        } else {
            return ChipSlidingDir.LOCKED;
        }
    }
    
    private int getChipIdByNum(int num) {
        for (int i = 0; i < N; i++) {
            if (mField[i].mNum == num) {
                return i;
            }
        }
        
        return -1;
    }
    
    public int getMovesCnt() {
        return mMovesCnt;
    }
    
    public float getSlide() {
        return mSlidingParam;
    }
    
    public ChipSlidingDir getSlidingDir() {
        return mDir;
    }
    
    public boolean isSliding(int chipNum) {
        for (Chip c : mSlidingChips) {
            if (c.mNum == chipNum) {
                return true;
            }
        }
        
        return false;
    }
    
    private boolean isSolvable() {
        
        int r = 0;
        
        for (int i = 0; i < N; i++) {
            int num = mField[i].mNum;
            
            if (num == 0) {
                continue;
            }

            // empty chip - get its row (from 1 to 4)
            if (num == N - 1) {
                r += i / Dim + 1;
            } else {
                for (int j = i + 1; j < N; j++) {
                    if (mField[j].mNum < num) {
                        r++;
                    }
                }
            }
        }

        return (r & 1) == (Dim & 1);
    }
    
    boolean isSolved() {
        for (int i = 0; i < N; i++) {
            if (mField[i].mNum != i) {
                return false;
            }
        }
        return true;
    }
    
    @SuppressWarnings("unused")
    private void resolveSolvability() {
        while (!isSolvable()) {
            rotate();
        }
    }
    
    private void rotate() {
        final Chip[] t = mField.clone();
        
        int rotInd = 0;
        for (int i = 0; i < ALLOWED_DIMS.length; i++) {
            if (ALLOWED_DIMS[i] == Dim) {
                rotInd = i;
                break;
            }
        }
        
        for (int i = 0; i < N; i++) {
            mField[i] = t[ROTATE_IND[rotInd][i]];
        }
    }
    
    public void setSlide(float dt) {
        mSlidingParam = dt;
    }
    
    public void setSlidingChips(int tracedChipNum) {
        
        mSlidingChips.clear();
        
        if ((tracedChipNum < 0) || (tracedChipNum >= N)) {
            return;
        }
        
        int chipId = -1;

        for (int i = 0; i < N; i++) {
            if (mField[i].mNum == tracedChipNum) {
                chipId = i;
                break;
            }
        }
        
        if ((chipId < 0) || (chipId >= N)) {
            return;
        }

        int row = chipId / Dim;
        int column = chipId % Dim;

        Chip n = null;

        mDir = getChipDir(chipId);
        
        switch (mDir) {
        case LEFT:
            while (column >= 0) {
                n = getChip(row * Dim + column);

                if (n != null) {
                    mSlidingChips.add(n);

                    if (n.mNum == N - 1) {
                        break;
                    }
                } else {
                    break;
                }

                column--;
            }
            break;

        case RIGHT:
            while (column < Dim) {
                n = getChip(row * Dim + column);

                if (n != null) {
                    mSlidingChips.add(n);

                    if (n.mNum == N - 1) {
                        break;
                    }
                } else {
                    break;
                }

                column++;
            }
            break;

        case DOWN:
            while (row >= 0) {
                n = getChip(row * Dim + column);

                if (n != null) {
                    mSlidingChips.add(n);

                    if (n.mNum == N - 1) {
                        break;
                    }
                } else {
                    break;
                }

                row--;
            }
            break;

        case UP:
            while (row < Dim) {
                n = getChip(row * Dim + column);

                if (n != null) {
                    mSlidingChips.add(n);

                    if (n.mNum == N - 1) {
                        break;
                    }
                } else {
                    break;
                }

                row++;
            }
            break;
        }

        if (Log.isLoggable(LogTag.TAG, Log.INFO)) {
            StringBuilder sb = new StringBuilder();
            for (Chip ch : mSlidingChips) {
                sb.append(ch.mNum + " ");
            }
            Log.i(LogTag.TAG, sb.toString());
        }
    }
    
    public boolean updateChipMoves(double deltaTime) {
        
        if ((mSlidingChips == null) || (mSlidingChips.size() < 2)) {
            return false;
        }
        
        boolean isMoving = false;

        if (mSlidingParam > 1 - NpMath.ZERO) {

            int i = mSlidingChips.size() - 1;
            
            Chip c = mSlidingChips.get(i);
            
            while (true) {
                i--;
                
                Chip n = mSlidingChips.get(i);
                
                c.mNum = n.mNum;
                
                if (i == 0) {
                    n.mNum = N - 1;
                    
                    mChip15 = getChipIdByNum(N - 1);
                    
                    mMovesCnt++;
                    
                    mSoundMan.playSound(R.raw.beep);
                    
                    break;
                } else {
                    c = n;
                }
            }

            mDir = ChipSlidingDir.LOCKED;
            mSlidingParam = 0;
            mSlidingChips.clear();
            
        } else if (mSlidingParam < NpMath.ZERO) {
            mDir = ChipSlidingDir.LOCKED;
            mSlidingParam = 0;
            mSlidingChips.clear();
        } else {
            boolean shouldDec = mSlidingParam < 0.5;
            mSlidingParam += (shouldDec) ? -deltaTime : deltaTime;
            
            isMoving = true;
        }
        
        return isMoving;
    }
}